/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package com.athena.objects;

import com.smartfoxserver.v2.SmartFoxServer;
import com.smartfoxserver.v2.entities.Room;
import com.smartfoxserver.v2.entities.User;
import com.smartfoxserver.v2.entities.data.ISFSArray;
import com.smartfoxserver.v2.entities.data.ISFSObject;
import com.smartfoxserver.v2.entities.data.SFSArray;
import com.smartfoxserver.v2.entities.data.SFSObject;
import com.smartfoxserver.v2.extensions.ExtensionLogLevel;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import sfs2x.extension.fourinarow.main.FourinarowExtension;
import sfs2x.extension.fourinarow.main.TaskRunner;
import sfs2x.extension.fourinarow.utils.Commands;

/**
 *
 * @author dodinhchien
 */
public class Game {
    private Room room;
    private boolean  started = false;
    private Player whoseTurn;
    public static final int BOARD_SIZE_X = 7;
    public static final int BOARD_SIZE_Y = 6;
    private final Tile[][] board;
    private int winner = 0;
    private int moveCount;
    private int size;
    private ScheduledFuture<?> taskTurnHandle;
    private ConcurrentHashMap<Integer, Player> players;

    public int getSize() {
        return size;
    }

    public void setSize(int size) {
        this.size = size;
    }
    

    public ConcurrentHashMap<Integer, Player> getPlayers() {
        return players;
    }
    
    public Room getRoom() {
        return room;
    }
    
    public int getWinner() {
        return winner;
    }

    public void setWinner(int winner) {
        this.winner = winner;
    }
    
    private int mark;

    public int getMark() {
        return mark;
    }

    public void setMark(int mark) {
        this.mark = mark;
    }

    public int getMoveCount() {
        return moveCount;
    }

    public void setMoveCount(int moveCount) {
        this.moveCount = moveCount;
    }

    public void setWhoseTurn(Player whoseTurn) {
        this.whoseTurn = whoseTurn;
    }

    public Player getWhoseTurn() {
        return whoseTurn;
    }

    public void setStarted(boolean started) {
        this.started = started;
    }

    public boolean isStarted() {
        return started;
    }

    public Game(Room room, int mark) {
        players = new ConcurrentHashMap<>();
        this.room = room;
        this.mark = mark;
        board = new Tile[BOARD_SIZE_X][BOARD_SIZE_Y];
    }

    void reset() {
        winner = 0;

        for (int x = 0; x < BOARD_SIZE_X; x++) {
            Tile[] boardRow = board[x];

            for (int y = 0; y < BOARD_SIZE_Y; y++) {
                boardRow[y] = Tile.EMPTY;
            }
        }
    }
    void setTileAt(int x, int y, Tile tile) {
        checkCoords(x, y);
        board[x][y] = tile;
    }
    
    private void checkCoords(int x, int y) {
        if (x < 0 || x >= BOARD_SIZE_X) {
            throw new IllegalArgumentException("Tile X position out of range: " + x);
        }

        if (y < 0 || y >= BOARD_SIZE_Y) {
            throw new IllegalArgumentException("Tile Y position out of range: " + y);
        }
    }
    
    Tile getTileAt(int x, int y) {
        return board[x][y];
    }
    int getTileAvailableByCol(int x) {
        for (int y = BOARD_SIZE_Y - 1; y >= 0; y--) {
            if (getTileAt(x, y) == Tile.EMPTY) {
                return y;
            }
        }
        return -1;
    }

    
    public GameState getGameStatus(int moveCount, int x, int y) {
        GameState state = GameState.RUNNING;

        //check winner 
        winner = checkVertical(x, y);

        if (winner == 0) {
            winner = checkHozital(x, y);
        }

        if (winner == 0) {
            winner = checkCrossRight(x, y);
        }

        if (winner == 0) {
            winner = checkCrossLeft(x, y);
        }

        if (winner != 0) {
            state = GameState.END_WITH_WINNER;
        }

        if (winner == 0 && moveCount == BOARD_SIZE_X * BOARD_SIZE_Y) {
            state = GameState.END_WIDTH_DRAW;
        }

        return state;
    }
    
    private int checkVertical(int x, int y) {
        int id = getTileAt(x, y).getId();
        int countUp = 0;
        int coorCheck = y + 1;

        while (countUp < 3 && coorCheck < BOARD_SIZE_Y) {
            Tile tUp = getTileAt(x, coorCheck);
            if (tUp.getId() == id) {
                countUp++;
                coorCheck++;
            } else {
                break;
            }
        }
        if (countUp == 3) {
            return id;
        }

        int countDown = 0;
        coorCheck = y - 1;
        while (countDown < 3 - countUp && coorCheck >= 0) {
            Tile tDown = getTileAt(x, coorCheck);

            if (tDown.getId() == id) {
                countDown++;
                coorCheck--;
            } else {
                break;
            }
        }
        return (countUp + countDown >= 3) ? id : 0;
    }

    private int checkHozital(int x, int y) {
        int id = getTileAt(x, y).getId();
        int countUp = 0;
        int coorCheck = x + 1;
        while (countUp < 3 && coorCheck < BOARD_SIZE_Y) {
            Tile tUp = getTileAt(coorCheck, y);
            if (tUp.getId() == id) {
                countUp++;
                coorCheck++;
            } else {
                break;
            }
        }
        if (countUp == 3) {
            return id;
        }

        int countDown = 0;
        coorCheck = x - 1;
        while (countDown < 3 - countUp && coorCheck >= 0) {
            Tile tDown = getTileAt(coorCheck, y);
            if (tDown.getId() == id) {
                countDown++;
                coorCheck--;
            } else {
                break;
            }
        }
        return (countUp + countDown >= 3) ? id : 0;
    }

    private int checkCrossRight(int x, int y) {
        int id = getTileAt(x, y).getId();
        int countUp = 0;
        int coorCheckX = x + 1;
        int coorCheckY = y + 1;
        while (countUp < 3 && coorCheckX < BOARD_SIZE_X && coorCheckY < BOARD_SIZE_Y) {
            Tile tUp = getTileAt(coorCheckX, coorCheckY);
            if (tUp.getId() == id) {
                countUp++;
                coorCheckX++;
                coorCheckY++;
            } else {
                break;
            }
        }
        if (countUp == 3) {
            return id;
        }

        int countDown = 0;
        coorCheckX = x - 1;
        coorCheckY = y - 1;
        while (countDown < 3 - countUp && coorCheckX >= 0 && coorCheckY >= 0) {
            Tile tDown = getTileAt(coorCheckX, coorCheckY);
            if (tDown.getId() == id) {
                countDown++;
                coorCheckX--;
                coorCheckY--;
            } else {
                break;
            }
        }
        return (countUp + countDown >= 3) ? id : 0;
    }

    private int checkCrossLeft(int x, int y) {
        int id = getTileAt(x, y).getId();
        int countUp = 0;
        int coorCheckX = x + 1;
        int coorCheckY = y - 1;
        while (countUp < 3 && coorCheckX < BOARD_SIZE_X && coorCheckY >= 0) {
            Tile tUp = getTileAt(coorCheckX, coorCheckY);
            if (tUp.getId() == id) {
                countUp++;
                coorCheckX++;
                coorCheckY--;
            } else {
                break;
            }
        }
        if (countUp == 3) {
            return id;
        }

        int countDown = 0;
        coorCheckX = x - 1;
        coorCheckY = y + 1;
        while (countDown < 3 - countUp && coorCheckX >= 0 && coorCheckY < BOARD_SIZE_Y) {
            Tile tDown = getTileAt(coorCheckX, coorCheckY);
            if (tDown.getId() == id) {
                countDown++;
                coorCheckX--;
                coorCheckY++;
            } else {
                break;
            }
        }
        return (countUp + countDown >= 3) ? id : 0;
    }
    
    public void Start(FourinarowExtension extension){
        if(started){
            throw new IllegalStateException("Game is already started!");
        }
        started = true;
        reset();
        size = players.size();
        for (int i = 0; i < room.getUserList().size(); i++) {
            Player p = players.get(room.getUserList().get(i).getId());
            p.setPlayerId(i+1);
        }
        ISFSObject resObj = new SFSObject();
        ISFSArray array = new SFSArray();
        
        for(Enumeration<Player> e = players.elements(); e.hasMoreElements();){
            Player p = e.nextElement();
            if(whoseTurn == null){
                whoseTurn = p;
            }
            array.addUtfString(p.getUsername());
        }
        resObj.putUtfString("t", whoseTurn.getUsername());
        resObj.putSFSArray("arr", array);
        
        countTimeTurn(extension);
        extension.send(Commands.START, resObj, room.getUserList());
    }
    
    public void moveHandle(FourinarowExtension extension, Player player, ISFSObject params) throws SQLException {
        int moveX = params.getInt("x");
        int moveY = getTileAvailableByCol(moveX);

        if (started) {
            if (whoseTurn == player) {
                if (getTileAt(moveX, moveY) == Tile.EMPTY) {

                    // Set game board tile
                    setTileAt(moveX, moveY, player.getPlayerId() == 1 ? Tile.GREEN : Tile.RED);

                    // Send response
                    ISFSObject respObj = new SFSObject();
                    respObj.putInt("x", moveX);
                    respObj.putInt("y", moveY);
                    respObj.putUtfString("t", player.getUsername());

                    extension.send(Commands.CMD_MOVE, respObj, room.getUserList());

                    // Increse move count and check game status					
                    increaseMoveCount();

                    stopTimer();

                    // Switch turn
                    updateTurn(extension);

                    // Check if game is over
                    checkBoardState(extension, moveX, moveY);

                }
            } else {
                extension.trace(ExtensionLogLevel.WARN, "Wrong turn error. It was expcted: " + whoseTurn + ", received from: " + player);
            }
        } else {
            extension.trace(ExtensionLogLevel.WARN, "game not started");
        }
    }
    
    public void countTimeTurn(FourinarowExtension extension){
        taskTurnHandle = SmartFoxServer.getInstance().getTaskScheduler().schedule(new TaskRunner(extension, this), 5,TimeUnit.SECONDS);
    }
    void increaseMoveCount() {
        moveCount += 1;
    }
    void stopTimer(){
        if (!taskTurnHandle.isCancelled()) {
            taskTurnHandle.cancel(true);
        }
    }
    void updateTurn(FourinarowExtension extension) {
        int ind = (whoseTurn.getPlayerId() == size) ? 1 : whoseTurn.getPlayerId() + 1;
        for(Enumeration<Player> e = players.elements(); e.hasMoreElements();){
            Player p = e.nextElement();
            if(p.getPlayerId() == ind){
                whoseTurn = p;
                break;
            }
        }
    }
    
    void checkBoardState(FourinarowExtension extension, int x, int y) throws SQLException {
        GameState state = getGameStatus(moveCount, x, y);
        if (state == GameState.END_WITH_WINNER) {
            int winnerId = winner;

            extension.trace("Winner found: ", winnerId);

            // Stop game
            stopGame();

            // Send update
            ISFSObject respObj = new SFSObject();
            ISFSArray array = new SFSArray();
            List<User> users = new ArrayList<>();
            List<Player> pDis = new ArrayList<>();
            for(Enumeration<Player> e = players.elements(); e.hasMoreElements();){
                Player player = e.nextElement();
                ISFSObject object = new SFSObject();
                object.putUtfString("N", player.getUsername());
                if(player.getPlayerId() == winnerId){
                    int fee = 0;
                    for(int xi = 0 ; xi < extension.getArrayMC().size(); xi++){
                        if(extension.getArrayMC().getSFSObject(xi).getInt("mark") == mark){
                            fee = extension.getArrayMC().getSFSObject(xi).getInt("fee");
                            break;
                        }
                    }
                    player.updateMark(extension, mark-fee);
                    object.putBool("w", true);
                }else{
                    player.updateMark(extension, -mark);
                    object.putBool("w", false);
                }
                if(player.getMark() < mark){
                    if(!player.isDisconnect())
                        users.add(player.getSfsUser());
                }
                if(player.isDisconnect()){
                    pDis.add(player);
                }
                object.putInt("mark", player.getMark());
                array.addSFSObject(object);
            }
            respObj.putSFSArray("arr", array);
            extension.send(Commands.CMD_WIN, respObj, room.getUserList());
            for (Player pDi : pDis) {
                players.remove(pDi.getId());
            }
            if(users.size() > 0){
                for (User user : users) {
                    extension.getApi().leaveRoom(user, room);
                }
            }

        } else if (state == GameState.END_WIDTH_DRAW) {
            extension.trace("TIE!");

            // Stop game
            stopGame();
            List<Player> pDis = new ArrayList<>();
            for(Enumeration<Player> e = players.elements(); e.hasMoreElements();){
                Player player = e.nextElement();
                if(player.isDisconnect()){
                    pDis.add(player);
                }
            }
            for (Player pDi : pDis) {
                players.remove(pDi.getId());
            }
            // Send update
            ISFSObject respObj = new SFSObject();
            extension.send(Commands.CMD_DRAW, respObj, room.getUserList());

            // Set the last game ending for spectators joining after the end and before a new game starts
            //gameExt.setLastGameEndResponse(new LastGameEndResponse(Commands.CMD_DRAW, respObj));
        } else {
            if(!whoseTurn.isDisconnect())
                countTimeTurn(extension);
            else{
                autoMove(extension);
            }
        }
    }
    void stopGame() {
        started = false;
        moveCount = 0;
        whoseTurn = null;
    }
    
    public void autoMove(FourinarowExtension extension) throws SQLException {
        //stopTimer();
        //extension.trace("auto move");
        int x = -1;
        int y = -1;
        Random rand = new Random();
        int i = 0;
        while(y < 0  && i < 100){
            x = rand.nextInt(Game.BOARD_SIZE_X);
            y = getTileAvailableByCol(x);
            i++;
        }
        ISFSObject sObject = SFSObject.newInstance();
        sObject.putInt("x", x);
        moveHandle(extension, whoseTurn, sObject);
    }
}
